package com.doudou.zuul.filter;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.doudou.zuul.config.AppConfig;
import com.doudou.zuul.entity.AppAccess;
import com.doudou.zuul.utils.HttpClientUtils;
import com.doudou.zuul.utils.TokenUtils;
import com.doudou.zuul.utils.ZuulFilterRequestUtils;
import com.doudou.zuul.utils.ZuulFilterResponseUtils;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import com.zssj.redis.utils.RedisStringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/**
 * 用于刷新token
 */
@Component
public class RefreshTokenFilter extends ZuulFilter {


    private static final Logger LOGGER = LoggerFactory.getLogger(RefreshTokenFilter.class);

    @Autowired
    private AppConfig appConfig;

    @Autowired
    private RedisStringUtils redisStringUtils;

    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        return 0;
    }

    @Override
    public boolean shouldFilter() {

        RequestContext ctx = RequestContext.getCurrentContext();
        if (!ctx.sendZuulResponse()) {
            return false;
        }
        String requestUri = ZuulFilterRequestUtils.getRequestUri(ctx);
        if (requestUri.equals(appConfig.getLocalRefreshTokenPoint())) {
            return true;
        }
        return false;
    }

    @Override
    public Object run() throws ZuulException {

        //去请求检查token的合法性
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();

        //获取token
        String accessToken;
        try {
            accessToken = TokenUtils.extractAccessToken(request);
        } catch (Exception e) {
            LOGGER.info("刷新token的的时候检查到access_token非法{}", e);
            ZuulFilterResponseUtils.sendResponse(ctx, 1000, e.getMessage(), null);
            return null;
        }

        if (StringUtils.isEmpty(accessToken)) {
            LOGGER.info("刷新token的时候检查到access_token 为空");
            ZuulFilterResponseUtils.sendResponse(ctx, 1000, "access_token不能为空！", null);
            return null;
        }

        //校验token合法性
        //这里要处理异常remote
        boolean legal = checkAccessToken(accessToken, appConfig.getRemoteCheckTokenPoint());

        if (!legal) {
            //不合法，返回处理
            LOGGER.info("刷新token的时候检查到access_token 非法,或者已经过期！");
            ZuulFilterResponseUtils.sendResponse(ctx, 1000, "刷新token的的时候检查到access_token 非法,或者已经过期！", null);
            return null;
        }

        //合法进行token刷新
        Map<String, String> accessLoad = null;
        try {
            //因为进行了校验，不会出现异常
            accessLoad = TokenUtils.extractPlayLoad(accessToken);
        } catch (Exception e) {
            ZuulFilterResponseUtils.sendResponse(ctx, 1000, "token解析失败！", null);
            return null;
        }

        //获取refresh_token
        String refreshTokenKeyBefore = TokenUtils.extractRefreshTokenKey(accessLoad);
        String refreshToken = redisStringUtils.get(refreshTokenKeyBefore);

        //过期时间会额外的多出5分钟
        String blackListTokenKey = TokenUtils.extractBlackListTokenKey(accessLoad);
        if (StringUtils.isEmpty(refreshToken)) {
            //返回引导去登录
            //将access_token 放入黑名单中
            redisStringUtils.set(blackListTokenKey, accessToken, TokenUtils.BLACK_LIST_TIME);
            ZuulFilterResponseUtils.sendResponse(ctx, 600, "refresh_token过期，请重新登录", null);
            return null;
        }

        //检查refresh_token 是不是在黑名单
        String blackListFreshTokenKey = TokenUtils.extractBlackListRefreshTokenKey(accessLoad);

        if (checkRefreshTokenInBlacklist(blackListFreshTokenKey)) {
            redisStringUtils.set(blackListTokenKey, accessToken);
            ZuulFilterResponseUtils.sendResponse(ctx, 600, "sorry, your are in a black list", null);
            return null;
        }

        Map<String, String> result = null;
        try {
            result = sendRefreshToken(refreshToken, appConfig.getRemoteRefreshTokenPoint(), accessLoad.get("client_id"));
        } catch (Exception e) {
            e.printStackTrace();
            ZuulFilterResponseUtils.sendResponse(ctx, 600, e.getMessage(), null);
            return null;
        }

        redisStringUtils.set(blackListTokenKey, accessToken, TokenUtils.BLACK_LIST_TIME);
        redisStringUtils.set(blackListFreshTokenKey, refreshToken, TokenUtils.BLACK_LIST_TIME);

        if (null == result || result.size() == 0) {
            LOGGER.info("刷新token失败，引导去登录");
            ZuulFilterResponseUtils.sendResponse(ctx, 600, "获取token失败！", null);
            return null;
        }


        //将原来的access_token 放入黑名单过期时间为刷新token的预刷新时间， 将原来的refresh_token 放入黑名单，过期时间为refresh_token的时间
        String newAccessToken = result.get("access_token");
        String refreshTokenKeyAfter = TokenUtils.extractRefreshTokenKey(newAccessToken);
        redisStringUtils.set(refreshTokenKeyAfter, result.remove("access_token"), TokenUtils.REFRESH_TOKEN_CACHE_TIME);

        //返回token
        ZuulFilterResponseUtils.sendResponse(ctx, 0, "刷新token成功", result, null);
        return null;
    }

    private Boolean checkRefreshTokenInBlacklist(String blackListFreshTokenKey) {

        String s = redisStringUtils.get(blackListFreshTokenKey);
        if (StringUtils.isEmpty(s)) {
            return false;
        }
        return true;
    }


    private Map<String, String> sendRefreshToken(String refreshToken, String remoteRefreshTokenPoint, String clientId) {

        List<AppAccess> appAccesses = appConfig.getAppAccesses();

        if (CollectionUtils.isEmpty(appAccesses)) {
            throw new RuntimeException("初始化未配置相关的应用信息！");
        }
        String clientSecret = null;

        //获取必须参数
        for (int i = 0; i < appAccesses.size(); i++) {
            AppAccess appAccess = appAccesses.get(i);
            if (clientId.equals(appAccess.getAppId())) {
                clientSecret = appAccess.getAppSecret();
                break;
            }
        }
        if (StringUtils.isEmpty(clientSecret)) {
            throw new RuntimeException("初始化未配置相关的应用秘钥！");
        }
        String url = remoteRefreshTokenPoint + "?grant_type=refresh_token&refresh_token=" + refreshToken + "&client_id=" + clientId + "&client_secret=" + clientSecret;

        String s = null;
        try {
            s = HttpClientUtils.doPostKeyValue(url, null);
        } catch (Exception e) {
            e.printStackTrace();
            //处理异常
            throw new RuntimeException("刷新token失败！");
        }
        if (StringUtils.isEmpty(s)) {
            throw new RuntimeException("刷新token失败");
        }
        Map<String, String> parse = JSON.parseObject(s, Map.class);
        return parse;
    }

    private boolean checkAccessToken(String accessToken, String remoteCheckTokenPoint) {
        Map<String, String> map = new HashMap<>();
        map.put("token", accessToken);
        //应该将状态验证拿到这里来做不同处理
        String result = null;
        try {
            result = HttpClientUtils.doPostKeyValue(remoteCheckTokenPoint, map);
        } catch (Exception e) {
            return false;
        }
        if (StringUtils.isEmpty(result)) {
            return false;
        }

        JSONObject jsonObject = JSONObject.parseObject(result);
        Object active = jsonObject.get("active");
        if (active != null && (Boolean) active) {
            return true;
        }
        return true;
    }


    /**
     * 提取载荷
     *
     * @param token
     * @return
     */
    private Map<String, String> extractPlayLoad(String token) {
        String[] sp = token.split("\\.");
        if (sp == null || sp.length != 3 || StringUtils.isEmpty(sp[1])) {
            throw new RuntimeException("传入的token格式不对！");
        }
        Map<String, String> map = JSON.parseObject(new String(Base64.getDecoder().decode(sp[1])), Map.class);
        if (map == null || map.size() == 0) {
            throw new RuntimeException("传入的token非法！");
        }
        return map;
    }


    /**
     * 根据过期时间判断是不是刷新
     *
     * @param map
     * @return
     */
    private boolean tokenExpired(Map map) {
        int exp = (int) map.get("exp");
        long now = new Date().getTime() / 1000;
        if (now + 60 * 1000 < exp) {
            return true;
        }
        return false;
    }


    private String[] extractAndDecodeHeader(String header, HttpServletRequest request) throws IOException {
        //取出basic前缀
        byte[] base64Token = header.substring(6).getBytes("UTF-8");
        byte[] decoded;
        try {
            decoded = Base64.getDecoder().decode(base64Token);
        } catch (IllegalArgumentException var7) {
            throw new RuntimeException("Failed to decode basic authentication token");
        }
        String token = new String(decoded, "UTF-8");
        int delim = token.indexOf(":");
        if (delim == -1) {
            throw new RuntimeException("Invalid basic authentication token");
        } else {
            return new String[]{token.substring(0, delim), token.substring(delim + 1)};
        }
    }
}
